import java.io.Serializable;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;



/**
 * Game implements UnicastRemoteObject so that it can be exported using 
 * JRMP which is the protocol RMI is running on top of
 * @author Simen
 *
 */
public class TicTacPlayer extends UnicastRemoteObject implements ITicTacPlayer,Serializable{

	private static final long serialVersionUID = 1L;
	private TicTacToeGui gui;		//GUI, handles game-logic/input/output 
	private String address="localhost";		
	private int port;
	
	public TicTacPlayer() throws RemoteException{
		this.gui=new TicTacToeGui(this);	
		this.port=gui.requestInt("Wich port will you be listening to?","3200");
		try {
			this.address=InetAddress.getLocalHost().getHostAddress();	//Get the LAN-address this application is running on
			startServer();
		} catch (UnknownHostException e) {
			gui.showErrorMessage(e.getMessage());
		}
	}	
	
	/**
	 * Starts the local server, starts listening for connections
	 * @param address Which address to listen on
	 * @param port	Which port to listen on
	 */
	private void startServer(){
		System.setSecurityManager(new LiberalSecurityManager());	
		bind();
	}
	


	/**
	 * When the remote player has clicked this method is called
	 */
	@Override
	public void playerClicked(int row, int column)
			throws RemoteException {
		gui.setMark(row, column, gui.getOtherMark());	//Update the board
		
		if(!gui.checkBoard()){ //Check for winner
			gui.setIsPlaying(this);
		}

	}
	


	/**
	 * Called when someone wants to connect to this object
	 * @param remotePlayer	The client that wants to connect
	 */
	@Override
	public void initiate(ITicTacPlayer remotePlayer)
			throws RemoteException {
		gui.setOtherPlayer(remotePlayer);
		newGame();
	}


	/**
	 * When the remote player requests a new game, this method is called
	 * An acknowledgment is sent back with some info regards who should start/who uses what mark
	 */
	@Override
	public void newGame()  {	
		try {	
			if(gui.showChallengedDialog(gui.getOtherName())){
				boolean shouldStart=Math.random()<0.5;
				boolean mark=Math.random()<0.5||gui.getMark()=='X';
				gui.clearBoard();
				gui.setMark(mark?'X':'O');
				gui.setIsPlaying(shouldStart?this:gui.getOtherPlayer());
				gui.getOtherPlayer().accept(mark?'O':'X', shouldStart);
				unbind();
		
			}else{				
				gui.getOtherPlayer().decline();
				gui.setOtherPlayer(null);
			}
		} catch (RemoteException e) {
			gui.showErrorMessage(e.getMessage());
		}
	}	
	
	/**
	 * This method is called from the remotePlayer if it accepts your challenge
	 */
	@Override
	public void accept(char mark,boolean start) {
		gui.clearBoard();
		try {
			gui.println(gui.getOtherPlayer().getName()+" accepted");
			gui.setMark(mark);			
			gui.setIsPlaying(start?gui.getOtherPlayer():this);
			unbind();
		} catch (RemoteException e) {
			gui.showErrorMessage(e.getMessage());
		}		
	}


	@Override
	public String getName() throws RemoteException {
		return gui.getName();
	}



	@Override
	public char getMark() throws RemoteException {
		return gui.getMark();
	}


	/**
	 * Show a message/rebind if your request was declined
	 */
	@Override
	public void decline() throws RemoteException {
		gui.showErrorMessage("Your request was declined");
	}

	/**
	 * IF your opponent quits the game, rebind/show a message
	 */
	@Override
	public void quit() throws RemoteException {
		gui.setOtherPlayer(null);
		gui.setIsPlaying(null);
		gui.showErrorMessage("Your opponent left the game");
	}
	
	
	public static void main(String[] args) {
		try {
			new TicTacPlayer();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	/**
	 * Unbinds this objects associated name from the registry so that it can't be reached from external sources
	 */
	public void unbind(){
		try {
			Naming.unbind("//"+address+":"+port+"/Server");
		} catch (RemoteException e) {
			gui.showErrorMessage(e.getMessage());
		} catch (MalformedURLException e) {
			gui.showErrorMessage(e.getMessage());
		} catch (NotBoundException e) {
			gui.showErrorMessage(e.getMessage());
		}
	}
	/**
	 * Binds this object to a name in the registry so that it can be reach from an external source
	 */
	public void bind(){
		try{
			LocateRegistry.createRegistry(port);	//Creates a registry on the running computer so that we can accept requests on the given port 	
		}catch(RemoteException e){}
		
		try {
			Naming.rebind("//"+address+":"+port+"/Server", this);	//Rebind "//"+address+":"+port+"/Server" to this instance so that others can obtain a reference to it
			gui.println("Listening on "+address+":"+port);
		} catch (RemoteException e) {
			gui.showErrorMessage(e.getMessage());
		} catch (MalformedURLException e) {
			gui.showErrorMessage(e.getMessage());
		}
		
	}

}
